/* * Copyright 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.network.sync.basicsyncadapter.provider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import com.example.android.network.sync.basicsyncadapter.util.SelectionBuilder; public class FeedProvider extends ContentProvider { FeedDatabase mDatabaseHelper; /** * Content authority for this provider. */ private static final String AUTHORITY = FeedContract.CONTENT_AUTHORITY; // The constants below represent individual URI routes, as IDs. Every URI pattern recognized by // this ContentProvider is defined using sUriMatcher.addURI(), and associated with one of these // IDs. // // When a incoming URI is run through sUriMatcher, it will be tested against the defined // URI patterns, and the corresponding route ID will be returned. /** * URI ID for route: /entries */ public static final int ROUTE_ENTRIES = 1; /** * URI ID for route: /entries/{ID} */ public static final int ROUTE_ENTRIES_ID = 2; /** * UriMatcher, used to decode incoming URIs. */ private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { sUriMatcher.addURI(AUTHORITY, "entries", ROUTE_ENTRIES); sUriMatcher.addURI(AUTHORITY, "entries/*", ROUTE_ENTRIES_ID); } @Override public boolean onCreate() { mDatabaseHelper = new FeedDatabase(getContext()); return true; } /** * Determine the mime type for entries returned by a given URI. */ @Override public String getType(Uri uri) { final int match = sUriMatcher.match(uri); switch (match) { case ROUTE_ENTRIES: return FeedContract.Entry.CONTENT_TYPE; case ROUTE_ENTRIES_ID: return FeedContract.Entry.CONTENT_ITEM_TYPE; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } /** * Perform a database query by URI. * * <p>Currently supports returning all entries (/entries) and individual entries by ID * (/entries/{ID}). */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = mDatabaseHelper.getReadableDatabase(); SelectionBuilder builder = new SelectionBuilder(); int uriMatch = sUriMatcher.match(uri); switch (uriMatch) { case ROUTE_ENTRIES_ID: // Return a single entry, by ID. String id = uri.getLastPathSegment(); builder.where(FeedContract.Entry._ID + "=?", id); case ROUTE_ENTRIES: // Return all known entries. builder.table(FeedContract.Entry.TABLE_NAME) .where(selection, selectionArgs); Cursor c = builder.query(db, projection, sortOrder); // Note: Notification URI must be manually set here for loaders to correctly // register ContentObservers. Context ctx = getContext(); assert ctx != null; c.setNotificationUri(ctx.getContentResolver(), uri); return c; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } /** * Insert a new entry into the database. */ @Override public Uri insert(Uri uri, ContentValues values) { final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); assert db != null; final int match = sUriMatcher.match(uri); Uri result; switch (match) { case ROUTE_ENTRIES: long id = db.insertOrThrow(FeedContract.Entry.TABLE_NAME, null, values); result = Uri.parse(FeedContract.Entry.CONTENT_URI + "/" + id); break; case ROUTE_ENTRIES_ID: throw new UnsupportedOperationException("Insert not supported on URI: " + uri); default: throw new UnsupportedOperationException("Unknown uri: " + uri); } // Send broadcast to registered ContentObservers, to refresh UI. Context ctx = getContext(); assert ctx != null; ctx.getContentResolver().notifyChange(uri, null, false); return result; } /** * Delete an entry by database by URI. */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SelectionBuilder builder = new SelectionBuilder(); final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); int count; switch (match) { case ROUTE_ENTRIES: count = builder.table(FeedContract.Entry.TABLE_NAME) .where(selection, selectionArgs) .delete(db); break; case ROUTE_ENTRIES_ID: String id = uri.getLastPathSegment(); count = builder.table(FeedContract.Entry.TABLE_NAME) .where(FeedContract.Entry._ID + "=?", id) .where(selection, selectionArgs) .delete(db); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } // Send broadcast to registered ContentObservers, to refresh UI. Context ctx = getContext(); assert ctx != null; ctx.getContentResolver().notifyChange(uri, null, false); return count; } /** * Update an etry in the database by URI. */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SelectionBuilder builder = new SelectionBuilder(); final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); int count; switch (match) { case ROUTE_ENTRIES: count = builder.table(FeedContract.Entry.TABLE_NAME) .where(selection, selectionArgs) .update(db, values); break; case ROUTE_ENTRIES_ID: String id = uri.getLastPathSegment(); count = builder.table(FeedContract.Entry.TABLE_NAME) .where(FeedContract.Entry._ID + "=?", id) .where(selection, selectionArgs) .update(db, values); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } Context ctx = getContext(); assert ctx != null; ctx.getContentResolver().notifyChange(uri, null, false); return count; } /** * SQLite backend for @{link FeedProvider}. * * Provides access to an disk-backed, SQLite datastore which is utilized by FeedProvider. This * database should never be accessed by other parts of the application directly. */ static class FeedDatabase extends SQLiteOpenHelper { /** Schema version. */ public static final int DATABASE_VERSION = 1; /** Filename for SQLite file. */ public static final String DATABASE_NAME = "feed.db"; private static final String TYPE_TEXT = " TEXT"; private static final String TYPE_INTEGER = " INTEGER"; private static final String COMMA_SEP = ","; /** SQL statement to create "entry" table. */ private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedContract.Entry.TABLE_NAME + " (" + FeedContract.Entry._ID + " INTEGER PRIMARY KEY," + FeedContract.Entry.COLUMN_NAME_ENTRY_ID + TYPE_TEXT + COMMA_SEP + FeedContract.Entry.COLUMN_NAME_TITLE + TYPE_TEXT + COMMA_SEP + FeedContract.Entry.COLUMN_NAME_LINK + TYPE_TEXT + COMMA_SEP + FeedContract.Entry.COLUMN_NAME_PUBLISHED + TYPE_INTEGER + ")"; /** SQL statement to drop "entry" table. */ private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedContract.Entry.TABLE_NAME; public FeedDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } } }